package ftp

import (
	"fmt"
	"gin/func"
	"gin/model"
	"github.com/pkg/sftp"
	"golang.org/x/crypto/ssh"
	"os"
	"path/filepath"
	"runtime/debug"
	"strconv"
	"strings"
	"time"
)

type GgFtpS struct {
	dataParsing    *DataParsing
	ftpConnectInfo *model.FtpConnectInfo
	ftpInfo        *model.FtpInfo
	DataIndexM     *model.DataIndexM
	Log            *_func.StyleLog
	Ftp            *ftp
	DataUserS      *DataUserS
}

func NewGgFtp() *GgFtpS {

	return &GgFtpS{
		NewDataParsing(),
		model.NewFtpConnectInfo(),
		model.NewFtpInfo(),
		model.NewDataIndexM(),
		&_func.StyleLog{},
		&ftp{},
		NewDataUserS(),
		//Inject services
	}
}

type ftp struct {
	sshClient      *ssh.Client  //ssh客户端
	sshStatus      bool         //ssh客户端状态
	sshConnectNum  int          //ssh重复连接次数 默认 3
	sftpClient     *sftp.Client //sftp客户端
	sftpStatus     bool         //sftp客户端状态
	sftpConnectNum int          //sftp重复连接次数 默认 3
	config         struct {
		userName       string //用户名
		password       string //密码
		Address        string //ip地址或连接地址
		port           string //端口
		id             string //数据来源id
		remoteFilePath string //远程路径目录
		localDir       string //本地路径目录
		ftpType        string //FTP类型
	}

	Log *_func.StyleLog
}

type Woker struct {
	FtpId       int64 //用户FTPid
	UserId      int64
	FtpType     string // ftp类型
	FtpUserName string // 账号
	FtpPassword string // 密码
	FtpAddress  string // 地址
	Port        string // 端口

	UserStatus int //用户状态

	WokerProcessStatus  bool  //处理状态
	CloserProcessStatus bool  //关不 woker 状态
	FTPWokerStatus      bool  //FTPWoker启动状态
	WokerStartTime      int64 //woker启动时间
	WokerTopTime        int64 //woker上次通信时间
	WokerOutTime        int64 //woker超时时间

	SendChan    chan string `json:"-"` //发送数据
	ReceiveChan chan string `json:"-"` //接收数据

	FristConnectStatus bool   //FTP第一次连接状态
	TopConnectTime     int64  //上次连接时间
	DataUpdateTime     int64  //数据更新时间
	HeartbeatId        string //心跳ID
	HeartbeatStatus    bool
	Frist              bool //用户信息首次连接状态
}

type scumFile struct {
	name     string //文件名称
	size     int64  //文件大小
	status   bool   //文件删除状态
	dwStatus bool   //文件下载状态
	fileName string //文件后缀名
	fileTime string //文件时间
}

func (g *GgFtpS) GgWorker(woker *Woker) {
	id := strconv.FormatInt(woker.FtpId, 10)

	//发送创建成功
	woker.ReceiveChan <- "WokerCreatSuccess"

	//初始化日志id
	g.Log.NewLog(id)

	defer func() {
		if r := recover(); r != nil {

			fmt.Println("最后错误捕捉", r)
			fmt.Println("最后错误捕捉", string(debug.Stack()))
		}

	}()
	//异常捕获
	defer func() {
		var err string
		if r := recover(); r != nil {

			g.Log.FtpLogGer2("协程捕获错误", r)
			g.Log.FtpLogGer2("协程捕获错误", debug.Stack())
			//log.Printf("协程错误:", r)
			g.Log.FtpLogGer("删除主线程中的运行id")

			err = fmt.Sprintf("%v", r)
			fmt.Println("666", err)
			if err == "ssh: handshake failed: ssh: unable to authenticate, attempted methods [none password], no supported methods remain" {
				woker.ReceiveChan <- "UserErr"
				err = "账号或密码错误"
			}
			if err == "runtime error: invalid memory address or nil pointer dereference" {
				err = "IP地址或端口错误,错误原因:连接报错:无效地址"
			}
			if strings.Contains(err, "dial tcp: address") {
				err = "IP地址或端口错误,错误原因:连接失败"
			}
			if strings.Contains(err, "ssh: handshake failed") {
				err = "IP地址或端口错误,错误原因:连接报错"

			}
			if strings.Contains(err, "connect: connection refused") {
				woker.ReceiveChan <- "UserErr"
				err = "IP地址或端口错误,错误原因:连接被拒绝"
			}

			//发送异常关闭
			woker.ReceiveChan <- "tryCloser"

			//g.ftpInfo.UpdateFtpErr(woker.FtpId, err)
		} else {
			err = "信号停止"
		}
		g.Ftp.CloseConnect()
	}()

	g.Log.FtpLogGer("开始执行线程")
	g.Log.FtpLogGer2("收到数据", woker)

	fmt.Println("收到数据", woker)
	ConnectInfo := g.ftpConnectInfo.GetConnectInfo(woker.FtpId)

	g.Log.FtpLogGer2("连接信息", ConnectInfo.FtpDirectory)
	fmt.Println("连接信息", ConnectInfo.FtpDirectory)

	//处理日志储存目录
	var scumFtpDir string
	if _, err := os.Stat(ConnectInfo.FtpDirectory); os.IsNotExist(err) {
		// 目录不存在，创建目录
		id = strconv.FormatInt(woker.FtpId, 10)

		scumFtpDir = "public/ftpLog/log_" + id

		err := os.MkdirAll("public/ftpLog/log_"+id, 0755)
		if err != nil {

			g.Log.FtpLogGer2("创建目录错误", "public/ftpLog/log_"+id)
			fmt.Println("创建目录错误" + "public/ftpLog/log_" + id)
			panic("目录异常")
			return
		}
		//保存项目目录
		g.Log.FtpLogGer("保存目录")
		fmt.Println("保存目录")
		g.ftpConnectInfo.SaveDirectory(woker.FtpId, scumFtpDir)
		g.Log.FtpLogGer("保存目录成功")
		fmt.Println("保存目录成功")

	} else {
		// 目录已存在
		g.Log.FtpLogGer("目录已存在")
		scumFtpDir = ConnectInfo.FtpDirectory
		id = strconv.FormatInt(woker.FtpId, 10)
	}

	//初始化 ftp数据
	g.Ftp.Log = g.Log
	g.Ftp.config.id = id
	g.Ftp.config.userName = woker.FtpUserName
	g.Ftp.config.password = woker.FtpPassword
	g.Ftp.config.Address = woker.FtpAddress
	g.Ftp.config.port = woker.Port
	g.Ftp.config.localDir = scumFtpDir
	g.Ftp.config.ftpType = woker.FtpType

	//初始化FTP服务器连接服务相关配置
	g.Ftp.sshConnectNum = 3
	g.Ftp.sftpConnectNum = 3
	var downloadFile []scumFile

	//g.ftpInfo.UpdateFtpStatus(woker.FtpId, 2)
	_, status, err := g.Ftp.GetSshId()

	//处理状态  第一次连接后 用户才能再次 启动这个任务 执行 否者 外部无法执行这个任务
	if status == false {

		woker.ReceiveChan <- "FirstConnectFailed"
		//第一次连接失败 证明可能是账号密码错误等  不进行日志下载  第一次连接成功后才 处理
		panic(err)
		return
	} else {
		fmt.Println("发送数据")
		woker.ReceiveChan <- "FirstConnectSuccess"
		//连接成功处理
		//发送消息

		//修改为 事件结束 连接成功

	}

	fmt.Println("FTP连接成功")

	//用户列表数据处理 返回用户数据 用做本地数据 处理用户数据变更
	UserData := g.DataUserS.EventUserListData(woker.FtpId)
	g.DataUserS.EventUserEconnomyData(woker.FtpId, &UserData)
	//panic(0)
	//var UserData = []model.DataUser{}
	//由于不能在初始化的时候传递 log 进 内部方法 在这里 传递
	g.dataParsing.Log = g.Log
	//panic("断点结束")
	var fotSTtus = true
	var forChanStatus bool
	wokerXtNum := 0
	for fotSTtus {

		// 携程通信  接收外部数据
		fmt.Println("检测外部信号")

		//接收消息
		forChanStatus = true
		for forChanStatus {
			select {
			case response := <-woker.SendChan:
				switch response {
				case "UserStop":
					//用户主动关闭结束
					woker.ReceiveChan <- "endUserStop"
					g.Log.FtpLogGer3("用户主动关闭-接收到关闭信号-删除woker数据", woker)
					return
				case "xtOutStop":
					//心跳超时了 直接退出去
					woker.ReceiveChan <- "xtOutStop"
					return
				case "dataOutStop":
					woker.ReceiveChan <- "dataOutStop"
					//data超时了 直接退出去
					return
				case "XT":
					wokerXtNum = 0
					//data超时了 直接退出去
				default:
					g.Log.FtpLogGer(response)
				}
				break
			default:
				forChanStatus = false
				break
			}
		}
		if wokerXtNum > 60 {
			g.Log.FtpLogGer3("master心跳停止 超过10秒 强行停止", woker)
			return
		}
		wokerXtNum = wokerXtNum + 1

		//发送协程心跳
		woker.ReceiveChan <- "xintiao"

		//清空数据
		downloadFile = downloadFile[:0]

		//获取ftp连接id
		_, status, err := g.Ftp.GetSshId()

		if status == false {
			panic(err)
			return
		} else {

			//连接成功发送连接刷新状态

			woker.ReceiveChan <- "ConnectSuccess"

			//g.ftpConnectInfo.SaveConnectTime(woker.FtpId, 1)
		}

		//获取远程文件
		downloadFile, status, err = g.Ftp.GetFtpDownloadFile()
		if status == false {
			//远程获取失败 重新连接获取
			g.Log.FtpLogGer("获取远程文件失败" + err.Error())
			continue
		}
		//下载文件
		g.Ftp.DownloadFile(downloadFile)

		//保存文件下载时间
		g.ftpConnectInfo.SaveFileDownloadTime(woker.FtpId)

		g.Log.FtpLogGer("下载文件成功")
		fmt.Println("id:", id, "获取下载文件成功")
		/*
			文件下载 利用文件修改时间  来决定 需要解析的文件  防止 下载成功解析失败的问题

			每次解析成功记录一个解析时间   文件修改时间大于 解析时间的为需要解析的文件  然后在过滤 解析保存
		*/

		//获取本地下载的文件
		downloadFile := g.GetDownloadFile(woker.FtpId, scumFtpDir)

		g.Log.FtpLogGer(fmt.Sprintf("解析下载文件成功", "长度", len(downloadFile)))
		//fmt.Println("获取的文件", downloadFile)
		fmt.Println("id:", id, "获取下载文件成功", "长度", len(downloadFile))

		/*downloadFilex := scumFile{name: "chat_20240615000140.log", size: 123456, status: true, dwStatus: true}
		downloadFile := append(downloadFile, downloadFilex)

		//测试数据.....................
		downloadFilex = scumFile{name: "chat_20240614080127.log", size: 123456, status: true, dwStatus: true}
		downloadFile = append(downloadFile, downloadFilex)*/

		//.......................
		if len(downloadFile) > 0 {
			g.Log.FtpLogGer("开始解析数据")
			g.dataParsing.RunParsing(userData{userId: woker.UserId, ip: woker.FtpAddress, ftpInfoId: ConnectInfo.FtpInfoId}, downloadFile, &UserData)
		} else {
			g.Log.FtpLogGer("没有要下载的文件")
			fmt.Println("无文件")
		}
		str := strconv.FormatInt(woker.WokerOutTime, 10)
		g.Log.FtpLogGer(woker.HeartbeatId + "心跳超时:" + str)
		g.Log.FtpLogGer("等待30秒下载")
		//fmt.Println(downloadFile)

		//数据更新通知
		woker.ReceiveChan <- "dataUpdate"

		//更新心跳
		g.ftpInfo.SaveDataUpdatedTime(woker.FtpId)
		//60秒下载一次
		time.Sleep(3 * time.Second)
	}
}

// 获取id
func (f *ftp) GetSshId() (*sftp.Client, bool, error) {

	var i int
	var err error
	for i = 0; i < 2; i++ {
		if f.sshStatus == false {
			//设定sftp断开
			f.sftpStatus = false
			f.Log.FtpLogGer2("开始连接ssh", fmt.Sprintf("第", i+1, "次"))
			fmt.Println("开始连接ssh", "第", i+1, "次")
			f.sshClient, f.sshStatus, err = f.ConnectSsh()
			if f.sshStatus == false || err != nil {
				//如果获取连接错误就跳过循环重新获取
				f.Log.FtpLogGer2("开始连接ssh", fmt.Sprintf("获取ssh 客户端失败", "第", i+1, "次"))
				fmt.Println("获取ssh 客户端失败", "第", i+1, "次")
				continue
			}
		} else {
			f.Log.FtpLogGer2("连接ssh成功", fmt.Sprintf("连接ssh成功"+"第", i+1, "次"))
			fmt.Println("连接ssh成功", "第", i+1, "次", err)
			break
		}
	}
	if f.sshStatus == false { // 当 i 等于 3 且最后一次尝试仍然失败时
		f.Log.FtpLogGer2("连接ssh失败", fmt.Sprintf("进入目录失败,开始返回", "第", i+1, "次", err))
		f.sshStatus = false
		return nil, false, err // 返回失败结果
	}

	//sftp连接
	for i = 0; i < 4; i++ {
		if f.sftpStatus == false {
			fmt.Println("开始连接sftp", "第", i+1, "次")
			f.Log.FtpLogGer2("开始连接sftp", fmt.Sprintf("开始连接sftp", "第", i+1, "次"))
			f.sftpClient, f.sftpStatus, err = f.OpenSftp()
			if f.sftpStatus == false || err != nil {
				f.Log.FtpLogGer2("sftp连接失败", fmt.Sprintf("sftp连接失败", "第", i+1, "次"))
				fmt.Println(f.config.id, "sftp连接失败", "第", i+1, "次")
				continue
			}
		}
	}
	if f.sftpStatus == false { // 当 i 等于 3 且最后一次尝试仍然失败时
		f.Log.FtpLogGer2("连接ssh失败", fmt.Sprintf("进入目录失败,开始返回", "第", i+1, "次", err))
		f.sftpStatus = false
		return nil, false, err // 返回失败结果
	}
	return f.sftpClient, true, nil

}

// 获取sftp
func (f *ftp) GetFtpClient() (*sftp.Client, bool, error) {

	return f.GetSshId()
}

// 下载ftp文件
func (f *ftp) GetFtpDownloadFile() ([]scumFile, bool, error) {
	var scumFileArr []scumFile
	var currentDir []os.FileInfo
	var err error

	//fmt.Println("远程文件初始化状态:", scumFileArr)
	//读目录
	for i := 0; i < 4; i++ {
		currentDir, err = f.sftpClient.ReadDir(f.config.remoteFilePath)
		if err != nil {
			f.Log.FtpLogGer2("获取文件文件信息错误", fmt.Sprintf("读取文件文件信息错误", "第", i+1, "次", err))
			fmt.Println(f.config.id, "读取文件文件信息错误:", "第", i+1, "次", err)
			if err.Error() == "connection lost" {
				f.sshStatus = false
				return nil, false, err
			}
			if i > 3 {
				f.sshStatus = false
				return nil, false, err
			}

		} else {
			fmt.Println("获取文件文件成功", "第", i+1, "次", len(currentDir))
			break
		}
	}

	//fmt.Println("本地文件初始化状态:", scumFileArr)
	//远程文件提取
	for _, ftpFile := range currentDir {
		if ftpFile.IsDir() {
			//fmt.Println("目录列表为:", ftpFile.Name())
		} else {
			scumF := scumFile{name: ftpFile.Name(), size: ftpFile.Size()}
			scumFileArr = append(scumFileArr, scumF)
			//fmt.Println("目录列表为:", ftpFile.Name(), ftpFile.Size())
		}
	}

	//fmt.Println("远程文件为:", scumFileArr)

	//本地文件提取
	var localFileArr []scumFile
	// 使用 Walk 函数遍历目录下的所有文件和子目录
	err = filepath.Walk(f.config.localDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			fmt.Printf("Error accessing path %s: %v\n", path, err)
			panic(err.Error())
			return nil
		}

		// 如果是文件，打印文件名和文件大小
		if !info.IsDir() {
			localFile := scumFile{name: info.Name(), size: info.Size()}
			localFileArr = append(localFileArr, localFile)
			//fmt.Printf("本地文件: %s, Size: %d bytes\n", path, info.Size())
		}

		return nil
	})

	//fmt.Println("本地文件为:", localFileArr)

	//结合本地和远程文件 分析需要下载的文件
	var fileDownloadStatus bool //文件下载状态
	var downloadFileArr []scumFile
	var deleteFile bool //删除文件状态
	for _, ftpFile := range scumFileArr {
		//fmt.Println("处理文件:", ftpFile)
		fileDownloadStatus = true
		deleteFile = false
		for _, localFile := range localFileArr {
			if ftpFile.name == localFile.name {
				//fmt.Println("文件相同:", ftpFile.name, localFile.name)
				deleteFile = true
				if ftpFile.size == localFile.size {
					//大小相同  不下载
					fileDownloadStatus = false
					break
				} else {
					//大小不同 需要下载
					break
				}
			}
		}
		//fmt.Println("处理文件:", ftpFile, "状态:", fileDownloadStatus)
		if fileDownloadStatus {
			downloadFile := scumFile{name: ftpFile.name, size: ftpFile.size, status: deleteFile}
			downloadFileArr = append(downloadFileArr, downloadFile)
		}
	}

	return downloadFileArr, true, nil
}

// 打开ssh连接
func (f *ftp) ConnectSsh() (*ssh.Client, bool, error) {
	fmt.Println("打印配置信息", f.sshClient)
	fmt.Println("打印配置信息", f.config)
	config := &ssh.ClientConfig{
		User: f.config.userName,
		Auth: []ssh.AuthMethod{
			ssh.Password(f.config.password),
		},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
		Timeout:         time.Second * 30,
	}

	// 建立 SSH 连接
	client, err := ssh.Dial("tcp", f.config.Address+":"+f.config.port, config)
	if err != nil {
		f.Log.FtpLogGer2("SSH 连接错误:", err)
		fmt.Println("SSH 连接错误:", err)
		return client, false, err
	}

	return client, true, err
}

// 打开ftp连接
func (f *ftp) OpenSftp() (*sftp.Client, bool, error) {

	fmt.Println("打印ssh信息:", f.sshClient)
	//执行 3次 3次失败则 返回错误
	var sftpClientStatus = false
	var sftpClient *sftp.Client
	var err error
	for i := 0; i < f.sftpConnectNum; i++ {
		f.Log.FtpLogGer2("连接sftp", fmt.Sprintf("第", i+1, "次"))
		fmt.Println("连接sftp", "第", i+1, "次")
		sftpClient, err = sftp.NewClient(f.sshClient)
		if err != nil {
			f.Log.FtpLogGer2("连接sftp失败", err)
			fmt.Println("连接sftp失败", "第", i+1, "次")
			fmt.Println("错误信息", err)
		} else {
			f.Log.FtpLogGer2("连接sftp成功", fmt.Sprintf("第", i+1, "次"))
			fmt.Println("连接sftp成功", "第", i+1, "次")
			sftpClientStatus = true
			break
		}
	}
	info, _ := sftpClient.ReadDir("/")
	if f.config.ftpType == "GG" {
		f.config.remoteFilePath = "/" + info[0].Name() + "/SaveFiles/Logs"
	}
	if f.config.ftpType == "PP" {
		f.config.remoteFilePath = "/" + info[0].Name() + "/SCUM/Saved/SaveFiles/Logs"
	}
	if sftpClientStatus == false {
		return nil, false, err
	} else {
		return sftpClient, true, nil
	}
}

// 下载文件
func (f *ftp) DownloadFile(downloadFileArr []scumFile) {

	var remoteFile *sftp.File
	var err error
	var i int
	for key, file := range downloadFileArr {

		//重复三次打开文件  防错
		for i = 0; i < 4; i++ {
			remoteFile, err = f.sftpClient.Open(filepath.Join(f.config.remoteFilePath + "/" + file.name))
			if err != nil {
				continue
			} else {
				break
			}
		}
		if i >= 3 || err != nil {
			fmt.Println("打开文件错误:", err)
			downloadFileArr[key].dwStatus = false
			continue
		}

		//删除本地文件
		if file.status {
			// 删除文件  liunx 路径要更换
			err := os.Remove(filepath.Join(f.config.localDir + "/" + file.name))
			if err != nil {
				panic(err)
				fmt.Println("Error deleting file:", err)
				return
			}
		}

		//创建本地文件

		dstFile, err := os.Create(filepath.Join(f.config.localDir + "/" + file.name))
		if err != nil {
			fmt.Println("打开本地文件错误:", err)
			panic(err)
			return
		}

		n, err := remoteFile.WriteTo(dstFile)
		if err != nil {
			if err.Error() == "connection lost" {
				f.sftpStatus = false
			}
			fmt.Println("错误:", err)
			return
		}
		fmt.Println("下载大小:", n)
		downloadFileArr[key].dwStatus = true
	}
}

// 关闭连接
func (f *ftp) CloseConnect() {
	f.sftpClient.Close()
	f.sshClient.Close()
}

// 获取下载的文件
func (d *GgFtpS) GetDownloadFile(id int64, scumFtpDir string) []scumFile {
	var downloadFile []scumFile
	SmFtpConnectInfo := d.ftpConnectInfo.GetConnectInfo(id)
	filepath.Walk(scumFtpDir, func(path string, info os.FileInfo, err error) error {
		//fmt.Println("获取文件时间")
		if err != nil {
			//fmt.Println("错误")
			return err // 返回遍历过程中遇到的任何错误
		}

		// 判断当前遍历到的是否是文件（非目录）
		if info.IsDir() {
			return nil
		}
		modTime := info.ModTime().Unix()
		//fmt.Println("获取文件时间", modTime, SmFtpConnectInfo.ParsingAt)
		if modTime > SmFtpConnectInfo.ParsingAt {
			//fmt.Println("获取文件时间1", modTime, SmFtpConnectInfo.ParsingAt)
			downloadFilex := scumFile{name: info.Name(), size: info.Size(), status: true, dwStatus: false}
			downloadFile = append(downloadFile, downloadFilex)
		}

		// 如果不需要遍历子目录中的文件，可以在这里是返回nil以结束遍历
		// return nil

		// 否则，继续遍历
		return nil
	})
	//fmt.Println("所有文件", downloadFile)
	return downloadFile
}

//获取信息

//获取判断目录

//获取连接ftp
